package tech.veedo.ragdoll.processor;

import cn.hutool.core.util.StrUtil;
import cn.hutool.log.StaticLog;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import tech.veedo.ragdoll.config.PrintMode;
import tech.veedo.ragdoll.entity.GlobalResponseAdvice;
import tech.veedo.ragdoll.constant.ExceptionCode;

import java.util.Arrays;

/**
 * 错误信息处理器
 *
 * @author yif zhang
 * @version 1.0.6
 * @since 2022-04-19
 */
@RestControllerAdvice
public class ExceptionProcessor extends ResponseEntityExceptionHandler {

    /**
     * 错误发生时，是否返回前台错误信息
     */
    private Boolean showRealErrorMessage;

    /**
     * 打印错误日志的输出模式
     */
    private PrintMode mode;

    @ExceptionHandler(ArithmeticException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> arithmeticExceptionHandler(ArithmeticException ex) {
        return generateResponseTMP(ex, ExceptionCode.ARITHMETIC.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.ARITHMETIC.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(ArrayIndexOutOfBoundsException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> arrayIndexOutOfBoundsExceptionHandler(ArrayIndexOutOfBoundsException ex) {
        return generateResponseTMP(ex, ExceptionCode.ARRAY_INDEX_OUT_OF_BOUNDS.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.ARRAY_INDEX_OUT_OF_BOUNDS.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(ArrayStoreException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> arrayStoreExceptionHandler(ArrayStoreException ex) {
        return generateResponseTMP(ex, ExceptionCode.ARRAY_STORE.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.ARRAY_STORE.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(ClassCastException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> classCastExceptionHandler(ClassCastException ex) {
        return generateResponseTMP(ex, ExceptionCode.CLASS_CAST.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.CLASS_CAST.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(ClassNotFoundException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> classNotFoundExceptionHandler(ClassNotFoundException ex) {
        return generateResponseTMP(ex, ExceptionCode.CLASS_NOT_FOUND.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.CLASS_NOT_FOUND.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(CloneNotSupportedException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> cloneNotSupportedExceptionHandler(CloneNotSupportedException ex) {
        return generateResponseTMP(ex, ExceptionCode.CLONE_NOT_SUPPORTED.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.CLONE_NOT_SUPPORTED.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(EnumConstantNotPresentException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> enumConstantNotPresentExceptionHandler(EnumConstantNotPresentException ex) {
        return generateResponseTMP(ex, ExceptionCode.ENUM_CONSTANT_NOT_PRESENT.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.ENUM_CONSTANT_NOT_PRESENT.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(IllegalAccessException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> illegalAccessExceptionHandler(IllegalAccessException ex) {
        return generateResponseTMP(ex, ExceptionCode.ILLEGAL_ACCESS.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.ILLEGAL_ACCESS.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> illegalArgumentExceptionHandler(IllegalArgumentException ex) {
        return generateResponseTMP(ex, ExceptionCode.ILLEGAL_ARGUMENT.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.ILLEGAL_ARGUMENT.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(IllegalMonitorStateException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> illegalMonitorStateExceptionHandler(IllegalMonitorStateException ex) {
        return generateResponseTMP(ex, ExceptionCode.ILLEGAL_MONITOR.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.ILLEGAL_MONITOR.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(IllegalStateException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> illegalStateExceptionHandler(IllegalStateException ex) {
        return generateResponseTMP(ex, ExceptionCode.ILLEGAL_STATE.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.ILLEGAL_STATE.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(IllegalThreadStateException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> illegalThreadStateExceptionHandler(IllegalThreadStateException ex) {
        return generateResponseTMP(ex, ExceptionCode.ILLEGAL_THREAD_STATE.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.ILLEGAL_THREAD_STATE.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(IndexOutOfBoundsException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> indexOutOfBoundsExceptionHandler(IndexOutOfBoundsException ex) {
        return generateResponseTMP(ex, ExceptionCode.INDEX_OUT_OF_BOUNDS.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.INDEX_OUT_OF_BOUNDS.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(InstantiationException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> instantiationExceptionHandler(InstantiationException ex) {
        return generateResponseTMP(ex, ExceptionCode.INSTANTIATION.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.INSTANTIATION.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(InterruptedException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> interruptedExceptionHandler(InterruptedException ex) {
        return generateResponseTMP(ex, ExceptionCode.INTERRUPTED.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.INTERRUPTED.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(NegativeArraySizeException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> negativeArraySizeExceptionHandler(NegativeArraySizeException ex) {
        return generateResponseTMP(ex, ExceptionCode.NEGATIVE_ARRAY_SIZE.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.NEGATIVE_ARRAY_SIZE.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(NoSuchFieldException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> noSuchFieldExceptionHandler(NoSuchFieldException ex) {
        return generateResponseTMP(ex, ExceptionCode.NO_SUCH_FIELD.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.NO_SUCH_FIELD.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(NoSuchMethodException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> noSuchMethodExceptionHandler(NoSuchMethodException ex) {
        return generateResponseTMP(ex, ExceptionCode.NO_SUCH_METHOD.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.NO_SUCH_METHOD.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(NullPointerException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> nullPointerExceptionHandler(NullPointerException ex) {
        return generateResponseTMP(ex, ExceptionCode.NULL_POINTER.getErrorCode(), ex.getLocalizedMessage());
    }

    @ExceptionHandler(NumberFormatException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> numberFormatExceptionHandler(NumberFormatException ex) {
        return generateResponseTMP(ex, ExceptionCode.NUMBER_FORMAT.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.NUMBER_FORMAT.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(ReflectiveOperationException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> reflectiveOperationExceptionHandler(ReflectiveOperationException ex) {
        return generateResponseTMP(ex, ExceptionCode.REFLECTIVE_OPERATION.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.REFLECTIVE_OPERATION.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(RuntimeException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> runtimeExceptionHandler(RuntimeException ex) {
        if (showRealErrorMessage) {
                return generateResponseTMP(ex, ExceptionCode.RUNTIME.getErrorCode(),
                    StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.RUNTIME.getMessage():ex.getLocalizedMessage());
        } else {
            ex.printStackTrace();
            return generateResponseTMP(ex, ExceptionCode.UNEXPECTED.getErrorCode(),
                    ExceptionCode.UNEXPECTED.getMessage());
        }
    }

    @ExceptionHandler(SecurityException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> securityExceptionHandler(SecurityException ex) {
        return generateResponseTMP(ex, ExceptionCode.SECURITY.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.SECURITY.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(StringIndexOutOfBoundsException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> stringIndexOutOfBoundsExceptionHandler(StringIndexOutOfBoundsException ex) {
        return generateResponseTMP(ex, ExceptionCode.STRING_INDEX_OUT_OF_BOUNDS.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.STRING_INDEX_OUT_OF_BOUNDS.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(TypeNotPresentException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> typeNotPresentExceptionHandler(TypeNotPresentException ex) {
        return generateResponseTMP(ex, ExceptionCode.TYPE_NOT_PRESENT.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.TYPE_NOT_PRESENT.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(UnsupportedOperationException.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> unsupportedOperationExceptionHandler(UnsupportedOperationException ex) {
        return generateResponseTMP(ex, ExceptionCode.UNSUPPORTED_OPERATION.getErrorCode(),
                StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.UNSUPPORTED_OPERATION.getMessage():ex.getLocalizedMessage());
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public GlobalResponseAdvice<Object> exceptionHandler(Throwable ex) {
        if (showRealErrorMessage) {
            return generateResponseTMP(ex, ExceptionCode.UNEXPECTED.getErrorCode(),
                    StrUtil.isBlank(ex.getLocalizedMessage())? ExceptionCode.UNEXPECTED.getMessage():ex.getLocalizedMessage());
        } else {
            return generateResponseTMP(ex, ExceptionCode.UNEXPECTED.getErrorCode(),
                    ExceptionCode.UNEXPECTED.getMessage());
        }
    }

    private GlobalResponseAdvice<Object> generateResponseTMP(Throwable ex, Integer code, String msg) {
        showError(ex);
        return new GlobalResponseAdvice<>()
                .setStateCode(code)
                .setMessage(msg);
    }
    
    private void showError(Throwable e) {
        if (mode == PrintMode.STACK) {
            Arrays.stream(e.getStackTrace()).forEach(ex -> StaticLog.error(ex.toString()));
        } else {
            StaticLog.error(e.getMessage());
        }
    }


    public Boolean getShowRealErrorMessage() {
        return showRealErrorMessage;
    }

    public void setShowRealErrorMessage(Boolean showRealErrorMessage) {
        this.showRealErrorMessage = showRealErrorMessage;
    }

    public PrintMode getMode() {
        return mode;
    }

    public void setMode(PrintMode mode) {
        this.mode = mode;
    }
}
